#include "soft_types.h"
#include "Logger/Logger.h"
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <time.h>
#include <stdarg.h>
#include <errno.h>

#define LOG_PRINT(level, fmt, args)          \
do {\
    switch (level) \
    { \
        case ERROR: \
            printf("<E> [%s %d] :", __FUNCTION__, __LINE__);\
            break;\
        case WARN: \
            printf("<W> [%s %d] :", __FUNCTION__, __LINE__); \
            break; \
        case INFO: \
            printf("<I> [%s %d] :", __FUNCTION__, __LINE__); \
            break; \
        case DEBUG: \
            printf("<D> [%s %d] :", __FUNCTION__, __LINE__); \
            break; \
        default: \
            printf("<N> [%s %d] :", __FUNCTION__, __LINE__);\
            break; \
    } \
    vprintf(fmt, args); \
} while(0);

typedef struct __LOG_FILE_INFO_s__
{
    CHAR szFilePath[LOG_FILENAME_SIZE];
    BOOL bUsed;
    ULONG ulSize;
}LOG_FILE_INFO_s;

typedef struct __LOG_FILE_s__
{
    LOG_FILE_INFO_s stFile[MAX_LOG_NUM];
    ULONG FileNum;
    ULONG level;
    FILE *fp;
    UINT32 uIndex;
}LOG_FILE_s;

/* ref app_types define */
const CHAR *StrLevel[8] = {"N", "E", "W", "I", "D"};

LOG_FILE_s g_logfile;
pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;

INT32 Log_TryLock()
{
    return pthread_mutex_trylock(&log_mutex);
}

INT32 LogUnlock()
{
    return pthread_mutex_unlock(&log_mutex);
}

INT32 Log_FileCompare(const char *strsrc, const char *strdes)
{
    return (strncmp(strsrc, strdes, strlen(strsrc)));
}

void Log_WriteTime(INT32 level)
{
    CHAR buf[64];
    struct tm curData;
    time_t curTime;

    LOG_D("Begin\n");

    time(&curTime);
    localtime_r(&curTime,&curData);
    
    memset(buf,0,sizeof(buf));
    sprintf(buf,"[%d-%02d-%02d %02d:%02d:%02d] <%s> : ",
            curData.tm_year + 1900, curData.tm_mon + 1, curData.tm_mday, 
            curData.tm_hour, curData.tm_min, curData.tm_sec, StrLevel[level]);
    if(g_logfile.fp)
    {
        fwrite(buf,1,strlen(buf),g_logfile.fp);
    }
    
    LOG_D("End\n");
    
    return;
}

ULONG Log_IsDirExist(const CHAR *pcLogPath)
{
    INT32 fd;
    struct stat buf;
    
    LOG_D("Begin\n");
    
    if((fd = open(pcLogPath,O_RDONLY)) < 0)
    {
        LOG_E("open [%s] fail : %s\n", pcLogPath, strerror(errno));
        return STAT_ERR_DIR_NOT_EXIST; 
    }
    
    if(fstat(fd,&buf) < 0)
    {
        LOG_E("fstat fail, dir [%s] not exist : %s\n", pcLogPath, strerror(errno));
        close(fd);
        return STAT_ERR_DIR_NOT_EXIST;
    }
    
    close(fd);

    if(S_ISDIR(buf.st_mode))
    {
        return STAT_SUCCEED;
    }
    
    LOG_I("[%s] is not a dir\n", pcLogPath);
    LOG_D("End\n");
    
    return STAT_ERR_DIR_NOT_EXIST;
}

ULONG Log_mkdirs(char *pathname, int Mode) 
{
    char *splite = NULL;  
    
    LOG_D("Begin\n");
    do
    { 
        splite = strchr(pathname, '/');
        if (splite != NULL)
        {
            *splite = 0;
        }

        if (strlen(pathname) == 0)
        {
            pathname = splite + 1;
            continue;
        }

        LOG_I("Get pathname [%s]\n", pathname);
        if (access(pathname, F_OK) != 0)    
        {
            if (mkdir(pathname, Mode) != 0)
            {
                LOG_E("mkdir [%s] fail : %s\n", pathname, strerror(errno));
                return STAT_ERR_DIR_CREAT;
            }
        }
        else
        {
            LOG_I("dir [%s] is exist\n", pathname);
        }

    break;
    } while (1);

    if (splite != NULL)
    {
        splite++;
        if (chdir(pathname) != 0)
        {
            LOG_E("chdir %s fail : %s\n", pathname, strerror(errno));
            return STAT_ERR_DIR_CHANGE;
        }
        
        if (Log_mkdirs(splite, Mode) != 0)
        {
            LOG_E("Log_mkdirs [%s] fail\n", splite);
            if (chdir("../") != 0)
            {
                LOG_E("chdir ../ fail : %s\n", strerror(errno));
                return STAT_ERR_DIR_CHANGE;
            }
            if (rmdir(pathname) != 0)
            {
                LOG_E("rmdir [%s] fail\n", pathname);
            }
            return STAT_ERR_DIR_CREAT;
        }
    }
    
    LOG_D("End\n");
    
    return STAT_SUCCEED;
}

ULONG Log_CreatPath(const CHAR *pathname, UINT32 Mode)
{
    CHAR szPath[512] = {0};    
    ULONG ulRet = STAT_SUCCEED;    
    CHAR pwd[256] = {0};
    UINT32 start_index = 0;

    LOG_D("Begin\n");

    snprintf(szPath, sizeof(szPath), "%s", pathname);
        
    if (getcwd(pwd, sizeof(pwd)) == NULL)
    {
        LOG_E("getcwd fail : %s\n", strerror(errno));
        return STAT_ERR_DIR_GETPWD;
    }
    
    LOG_D("pwd %s\n", pwd);
    
    if (szPath[0] == '/')
    {
        start_index = 1;
        if (chdir("/") != 0)
        {
            LOG_E("chdir / fail : %s\n", strerror(errno));
            return STAT_ERR_DIR_CHANGE;
        }
    }
    else if (szPath[0] == '.' && szPath[1] == '/')
    {
        start_index = 2;
    }

    ulRet = Log_mkdirs(szPath + start_index, 0755);

    if (chdir(pwd) != 0)
    {
        LOG_E("chdir %s fail : %s\n", pwd, strerror(errno));
        return STAT_ERR_DIR_CHANGE;
    }
    
    LOG_D("End\n");
    
    return ulRet;
}

BOOL Log_FileFullCheck(FILE *fp, ULONG *pulLen)
{
    int filelen = 0;
    
    LOG_D("Begin\n");

    if(fp == NULL || pulLen == NULL)
    {
        LOG_E("invaild param\n");
        return -1;
    }
    
    fseek(fp,0,SEEK_END); //指到结尾，用于帮助获取文件大小
    filelen = ftell(fp);

    *pulLen = filelen;
    LOG_D("filelen %d, limit %d\n", filelen, LOG_FILE_SIZE);

    if(filelen > LOG_FILE_SIZE)
    {
        return BOOL_TRUE;
    }
    
    LOG_D("End\n");
    
    return BOOL_FALSE;
}

ULONG Log_CheckDir(const char *pathname)
{
    ULONG ulRet = 0;
    
    LOG_D("Begin\n");
    
    ulRet = Log_IsDirExist(pathname);
    if(ulRet != STAT_SUCCEED)
    {
        ulRet = Log_CreatPath(pathname, 0755);
    }
    
    LOG_D("End\n");
    
    return ulRet;
}

void Log_DelOldFile(void)
{
    int ret;
    int file_count = 0;
    LOG_FILE_INFO_s *OldFile = NULL;

    LOG_D("Begin\n");

    while(file_count <= g_logfile.FileNum)
    {
        if(g_logfile.stFile[file_count].bUsed == BOOL_TRUE)
        {
            if (OldFile == NULL)
            {
                OldFile = &g_logfile.stFile[file_count];
                continue;
            }
            
            ret = Log_FileCompare(OldFile->szFilePath, g_logfile.stFile[file_count].szFilePath);
            if(ret > 0)
            {
                OldFile = &g_logfile.stFile[file_count];
            }
        }
        file_count++;
    }

    unlink(OldFile->szFilePath);
    LOG_I("Delete [%s]\n", OldFile->szFilePath);

    memset(OldFile, 0, sizeof(*OldFile));
    g_logfile.FileNum--;
    
    LOG_D("End\n");

    return;
}

void Log_GetVaildPos(UINT32 *puPos)
{
    int file_count = 0;

    LOG_D("Begin\n");

    while(file_count <= g_logfile.FileNum)
    {
        if(g_logfile.stFile[file_count].bUsed == BOOL_FALSE)
        {
            break;
        }
        file_count++;
    }

    *puPos = file_count;
    
    LOG_D("End\n");
    
    return;
}

BOOL Log_CheckNameStart(char *fileName)
{
    CHAR *pcStart = NULL;
    
    LOG_D("Begin\n");
    
    pcStart = strstr(fileName, LOG_FILE_PREFIX);
    if (pcStart == NULL || pcStart != fileName)
    {
        return BOOL_FALSE;
    }

    LOG_I("logfile [%s] Start Match\n", fileName);
    LOG_D("End\n");

    return BOOL_TRUE;
}

BOOL Log_CheckNameEnd(char *fileName)
{
    CHAR *pcEnd = NULL;
    
    LOG_D("Begin\n");
    
    pcEnd = strstr(fileName, LOG_FILE_SUFFIX);
    if (pcEnd == NULL || pcEnd[strlen(LOG_FILE_SUFFIX)] != 0)
    {
        return BOOL_FALSE;
    }
    
    LOG_I("logfile [%s] End Match\n", fileName);
    LOG_D("End\n");
    
    return BOOL_TRUE;
}

ULONG Log_LoadPathInfo(char *pcLogPath)
{
    DIR *dir = NULL;
    struct dirent *dir_info = NULL;
    CHAR szFilePath[512] = {0};
    UINT32 uPos = 0;
    struct stat statbuf;
    ULONG ulRet = 0;
    struct tm curdata;

    LOG_D("Begin\n");

    ulRet = Log_CheckDir(pcLogPath);
    if (ulRet != STAT_SUCCEED)
    {
        LOG_E("Log_CheckDir fail\n");
        return ulRet;
    }
    
    dir = opendir(pcLogPath);
    if(dir == NULL)
    {
        LOG_E("opendir [%s] fail : %s\n", pcLogPath, strerror(errno));
        return STAT_ERR_DIR_OPEN;
    }
    
    while((dir_info = readdir(dir)) != NULL)
    {
        stat(dir_info->d_name, &statbuf);
        if ((S_ISREG(statbuf.st_mode) != 0) ||
            (strcmp(dir_info->d_name, ".") == 0) ||
            (strcmp(dir_info->d_name, "..") == 0))
        {
            continue;
        }
        
        LOG_D("Get file %s\n", dir_info->d_name);
        /* check file name format */
        if ((Log_CheckNameStart(dir_info->d_name) == BOOL_FALSE) ||
            (Log_CheckNameEnd(dir_info->d_name) == BOOL_FALSE) ||
            (sscanf(dir_info->d_name + strlen(LOG_FILE_PREFIX), "%04d%02d%02d_%02d_%02d_%02d",
                   &curdata.tm_year, &curdata.tm_mon, &curdata.tm_mday,
                   &curdata.tm_hour, &curdata.tm_min, &curdata.tm_sec) != 6))
                   
        {
            snprintf(szFilePath, sizeof(szFilePath), "%s/%s", pcLogPath, dir_info->d_name);
            LOG_I("remove %s\n", szFilePath);
            unlink(szFilePath);
            continue;
        }

        if(g_logfile.FileNum >= MAX_LOG_NUM)
        {
            Log_DelOldFile();
        }
        
        Log_GetVaildPos(&uPos);
        
        sprintf(g_logfile.stFile[uPos].szFilePath,"%s/%s",pcLogPath,dir_info->d_name);
        g_logfile.stFile[uPos].ulSize = statbuf.st_size;
        g_logfile.stFile[uPos].bUsed = BOOL_TRUE;
        g_logfile.FileNum++;
    }
    
    closedir(dir);
    dir = NULL;
    LOG_D("End\n");
    
    return STAT_SUCCEED;
}

ULONG Log_CreatNewFile(UINT32 uIndex)
{
    char filename[LOG_FILENAME_SIZE] = {0};
    struct tm curdata;
    time_t curtime;
    
    LOG_D("Begin\n");
    
    time(&curtime);
    localtime_r(&curtime,&curdata);
    sprintf(filename,"%s/%s%04d%02d%02d_%02d_%02d_%02d%s", LOG_FILE_DIR, LOG_FILE_PREFIX, 
            curdata.tm_year + 1900, curdata.tm_mon + 1, curdata.tm_mday,
            curdata.tm_hour, curdata.tm_min, curdata.tm_sec, LOG_FILE_SUFFIX);
    g_logfile.fp = fopen(filename,"wb+");
    if (g_logfile.fp == NULL)
    {
        LOG_E("fopen %s fail : %s\n", filename, strerror(errno));
        return STAT_ERR_FILE_OPEN;
    }
    
    memcpy(g_logfile.stFile[uIndex].szFilePath, filename, sizeof(g_logfile.stFile[uIndex].szFilePath));
    g_logfile.stFile[uIndex].ulSize = 0;
    g_logfile.stFile[uIndex].bUsed = BOOL_TRUE;
    g_logfile.FileNum++;
    
    LOG_I("Create [%s] succeed\n", filename);
    
    LOG_D("End\n");
    
    return STAT_SUCCEED;
}

BOOL Log_GetLastWriteFile(UINT32 *pulIndex)
{
    int ret;
    int file_count = 0;
    LOG_FILE_INFO_s *LastFile = NULL;

    LOG_D("Begin\n");

    while(file_count <= g_logfile.FileNum)
    {
        if(g_logfile.stFile[file_count].bUsed == BOOL_TRUE)
        {
            if (LastFile == NULL)
            {
                LastFile = &g_logfile.stFile[file_count];
                *pulIndex = file_count;
                continue;
            }
            
            ret = Log_FileCompare(LastFile->szFilePath, g_logfile.stFile[file_count].szFilePath);
            if(ret < 0)
            {
                LastFile = &g_logfile.stFile[file_count];
                *pulIndex = file_count;
            }
        }
        file_count++;
    }

    if (LastFile != NULL && LastFile->ulSize < LOG_FILE_SIZE)
    {
        return BOOL_TRUE;
    }
    
    LOG_D("End\n");
    
    return BOOL_FALSE;
}

ULONG Log_OpenStartFile(void)
{
    UINT32 uIndex = 0;

    LOG_D("Begin\n");

    if (Log_GetLastWriteFile(&uIndex) == BOOL_FALSE)
    {
        if (g_logfile.FileNum >= MAX_LOG_NUM)
        {
            Log_DelOldFile();
        }
        
        Log_GetVaildPos(&uIndex);
    }
    
    if (g_logfile.stFile[uIndex].bUsed == BOOL_TRUE)
    {
        LOG_I("open exist file [%s]\n", g_logfile.stFile[uIndex].szFilePath);
        g_logfile.fp = fopen(g_logfile.stFile[uIndex].szFilePath,"ab+");
        if (g_logfile.fp == NULL)
        {
            LOG_E("fopen %s fail\n", g_logfile.stFile[uIndex].szFilePath);
            return STAT_ERR_FILE_OPEN;
        }
    }
    else
    {
        LOG_D("create file\n");
        if (Log_CreatNewFile(uIndex) != 0)
        {
            LOG_E("Log_CreatNewFile fail\n");
            return STAT_ERR_FILE_CREAT;
        }
    }
    
    LOG_D("End\n");
    
    return STAT_SUCCEED;
}

ULONG Log_Init(int level)
{
    ULONG ulRet = STAT_SUCCEED;
    memset(&g_logfile,0,sizeof(g_logfile));
    
    LOG_D("Begin\n");
    
    ulRet = Log_LoadPathInfo(LOG_FILE_DIR);
    if (ulRet != STAT_SUCCEED)
    {
        LOG_E("Log_LoadPathInfo fail\n");
        return ulRet;
    }
    
    ulRet = Log_OpenStartFile();
    if (ulRet != STAT_SUCCEED)
    {
        LOG_E("Log_OpenStartFile fail\n");
        return ulRet;
    }

    g_logfile.level = level;

    LOG_D("End\n");

    return ulRet;   
}

ULONG Log_Out(INT32 trace_level, const CHAR *format, ...)
{
    va_list args;
    CHAR buffer[2048] = {0};
    UINT32 uIndex = 0;
    
    LOG_D("Begin\n");
    
    if(g_logfile.fp == NULL){
        LOG_E("Log file is not open\n");
        return STAT_ERR_FILE_OPEN;
    }
    
    if(trace_level > g_logfile.level)
    {
        return STAT_SUCCEED;
    }
    
    if (Log_TryLock() != 0)
    {
        LOG_D("lock busy\n");
        return STAT_ERR_THREAD_TRY_LOCK;
    }
    
    va_start(args,format);
    LOG_PRINT(trace_level, format, args);
    va_end(args);

    va_start(args,format);
    vsnprintf(buffer, sizeof(buffer), format, args);
    va_end(args);
   
    LOG_D("buffer [%s]\n", buffer);
    
    Log_WriteTime(trace_level);
    fwrite(buffer, 1, strlen(buffer), g_logfile.fp);
    
    if(Log_FileFullCheck(g_logfile.fp, &g_logfile.stFile[g_logfile.uIndex].ulSize) == BOOL_TRUE)
    {
        fclose(g_logfile.fp);
        g_logfile.fp = NULL;
        if (g_logfile.FileNum >= MAX_LOG_NUM)
        {
            Log_DelOldFile();
        }
        
        Log_GetVaildPos(&uIndex);
        if (Log_CreatNewFile(uIndex) != 0)
        {
            LOG_E("Log_CreatNewFile fail\n");
            return STAT_ERR_FILE_CREAT;
        }
    }
    
    LogUnlock();
    
    LOG_D("End\n");

    return 0;
}
